package com.whfc.common.util;

import com.whfc.common.exception.BizException;
import com.whfc.common.result.ResultEnum;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @author qzexing
 * @version 1.0
 * @date 2021-04-14 17:47
 */
public class JwtUtils {

    private static final Logger logger = LoggerFactory.getLogger(JwtUtils.class);

    /**
     * JWT加密秘钥
     */
    private static final String SECRET = "fcwl_jwt_secret";


    /**
     * 创建jwt加密字符串
     *
     * @param ttlMillis jwt过期时间
     * @param apiKey    key
     * @param apiSecret secret
     * @return jwt加密字符串
     */
    public static String createJwt(long ttlMillis, String apiKey, String apiSecret) {
        logger.info("创建JWT加密字符串, ttlMillis:{}, apiKey:{}, apiSecret:{}", ttlMillis, apiKey, apiSecret);
        String jwt = createJwt(ttlMillis, apiKey, apiSecret, null);
        logger.info("jwt加密token:{}", jwt);
        return jwt;
    }

    /**
     * 创建jwt加密字符串
     *
     * @param ttlMillis jwt过期时间
     * @param apiKey    key
     * @param apiSecret secret
     * @param claims    参数
     * @return jwt加密字符串
     */
    public static String createJwt(long ttlMillis, String apiKey, String apiSecret, Map<String, Object> claims) {
        logger.info("创建JWT加密字符串, ttlMillis:{}, apiKey:{}, apiSecret:{}, claims:{}", ttlMillis, apiKey, apiSecret, claims);
        //使用HS256算法
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

        //生成JWT的时间
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);

        //下面就是在为payload添加各种标准声明和私有声明了
        //这里其实就是new一个JwtBuilder，设置jwt的body
        JwtBuilder builder = Jwts.builder();
        if (claims != null && !claims.isEmpty()) {
            builder.setClaims(claims);
        }
        //设置jti(JWT ID)：是JWT的唯一标识，根据业务需要，这个可以设置为一个不重复的值，主要用来作为一次性token,从而回避重放攻击。
        builder.setId(UUID.randomUUID().toString())
                //iat: jwt的签发时间
                .setIssuedAt(now)
                .setIssuer(apiKey)
                .setSubject(apiSecret)
                // 设置签名使用的签名算法和签名使用的秘钥
                .signWith(signatureAlgorithm, SECRET);
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date exp = new Date(expMillis);
            //设置过期时间
            builder.setExpiration(exp);
        }
        String jwt = builder.compact();
        logger.info("jwt加密token:{}", jwt);
        return jwt;
    }


    /**
     * Token的解密
     *
     * @param token token
     * @return 解密
     */
    public static Claims parseJwt(String token) {
        logger.info("解析JWT加密token:{}", token);
        //签名秘钥，和生成的签名的秘钥一模一样
        //得到DefaultJwtParser
        try {
            Claims claims = Jwts.parser()
                    //设置签名的秘钥
                    .setSigningKey(SECRET)
                    //设置需要解析的jwt
                    .parseClaimsJws(token).getBody();
            logger.info("解密JWT,claims:{}", claims);
            Date expiration = claims.getExpiration();
            if (expiration.getTime() < System.currentTimeMillis()) {
                logger.info("JWT加密串已过期");
                throw new BizException(ResultEnum.AUTHZ_ERROR.getCode(), "token已过期");
            }
            return claims;
        } catch (Exception e) {
            logger.warn("parseJwt解密失败.", e);
            throw new BizException(ResultEnum.AUTHZ_ERROR.getCode(), "token解析错误");
        }
    }


    /**
     * 校验token
     * 在这里可以使用官方的校验，我这里校验的是token中携带的密码于数据库一致的话就校验通过
     *
     * @param token token
     * @return 验证token是否成功
     */
    public static boolean isVerify(String token) {
        logger.info("验证JWT加密token:{}", token);
        //签名秘钥，和生成的签名的秘钥一模一样
        //得到DefaultJwtParser
        try {
            Claims claims = Jwts.parser()
                    //设置签名的秘钥
                    .setSigningKey(SECRET)
                    //设置需要解析的jwt
                    .parseClaimsJws(token).getBody();
            logger.info("解密JWT,claims:{}", claims);
            Date expiration = claims.getExpiration();
            return expiration.getTime() < System.currentTimeMillis();
        } catch (Exception e) {
            logger.warn("isVerify解密失败.", e);
            return false;
        }
    }

    public static void main(String[] args) {
        String apiKey = "MAnRUAqL8ZeRGVhSIGYApusQYZACMKJm";
        String apiSecret = "RIHXwMYjBaO5LF4wGPhMDB7ePwAgMzAj";
        Map<String, Object> map = new HashMap<>();
        map.put("deptId", 2);
        String jwt = createJwt(7 * 24 * 60 * 60 * 1000, apiKey, apiSecret, map);
        Claims claims = parseJwt(jwt);
        System.out.println(claims);
    }
}
